package checker

import (
	"fmt"
	"mi/compiler/ast"
	"mi/compiler/binder"
)

type Type interface {
	ID() int
	Flags() TypeFlags
	Symbol() *binder.Symbol

	setID(id int)
	setFlags(v TypeFlags)
	orFlags(v TypeFlags)
	andFlags(v TypeFlags)
	hasFlags(v TypeFlags) bool

	setSymbol(v *binder.Symbol)
}

type tpe struct {
	flags  TypeFlags      // Flags
	id     int            // Unique ID
	symbol *binder.Symbol // Symbol associated with type (if any)
}

func (t *tpe) ID() int                { return t.id }
func (t *tpe) Flags() TypeFlags       { return t.flags }
func (t *tpe) Symbol() *binder.Symbol { return t.symbol }

func (t *tpe) setID(id int)               { t.id = id }
func (t *tpe) setFlags(v TypeFlags)       { t.flags = v }
func (t *tpe) orFlags(v TypeFlags)        { t.flags |= v }
func (t *tpe) andFlags(v TypeFlags)       { t.flags &= v }
func (t *tpe) hasFlags(v TypeFlags) bool  { return t.flags&v != 0 }
func (t *tpe) setSymbol(v *binder.Symbol) { t.symbol = v }

// Intrinsic types (TypeFlags.Intrinsic)
type intrinsicType struct {
	name string
	realName string
	tpe
}

type LiteralType interface {
	Type
	string() string
	aLiteralType()
}

type stringLiteralType struct {
	text string
	tpe
}
func (s*stringLiteralType) string() string { return s.text }
func (s*stringLiteralType) aLiteralType() {}

type numberLiteralType struct {
	value *Number
	tpe
}
func (s *numberLiteralType) string() string { return s.value.String() }
func (s *numberLiteralType) aLiteralType() {}

type ObjectType interface {
	Type
	Members() *binder.SymbolTable
	Fields() []*binder.Symbol
	Methods() []*binder.Symbol
	CallSignatures() []*Signature
	ConstructSignatures() []*Signature

	setMembers(v *binder.SymbolTable)
	setFields(v []*binder.Symbol)
	setMethods(v []*binder.Symbol)
	setCallSignatures(v []*Signature)
	setConstructSignatures(v []*Signature)
}

type objectType struct {
	members     *binder.SymbolTable // Members by name
	fields      []*binder.Symbol    // Fields
	methods     []*binder.Symbol    // Methods

	callSignatures      []*Signature // Call signatures of type
	constructSignatures []*Signature // Construct signatures of type
	tpe
}
func (o *objectType) Members() *binder.SymbolTable      { return o.members }
func (o *objectType) Fields() []*binder.Symbol          { return o.fields }
func (o *objectType) Methods() []*binder.Symbol         { return o.methods }
func (o *objectType) CallSignatures() []*Signature      { return o.callSignatures }
func (o *objectType) ConstructSignatures() []*Signature { return o.constructSignatures }

func (o *objectType) setMembers(v *binder.SymbolTable)      { o.members = v }
func (o *objectType) setFields(v []*binder.Symbol)          { o.fields = v }
func (o *objectType) setMethods(v []*binder.Symbol)         { o.methods = v }
func (o *objectType) setCallSignatures(v []*Signature)      { o.callSignatures = v }
func (o *objectType) setConstructSignatures(v []*Signature) { o.constructSignatures = v }

type notNullType struct {
	target Type
	tpe
}

type boundsType struct {
	target Type
	tpe
}

type inBoundsType struct {
	boundsType
}

type outBoundsType struct {
	boundsType
}

type noBoundsType struct {
	boundsType
}

type BaseTypeKind int
const (
	BTKExtends = iota
	BTKImplement
)

type BaseType struct {
	Type ObjectType
	Kind BaseTypeKind
}

type InterfaceType interface {
	ObjectType
	TypeParameters() []*typeParameter
	DeclaredMembers() *binder.SymbolTable

	setTypeParameters(v []*typeParameter)
	baseTypes() []BaseType
	addBaseType(v BaseType)
	hasBaseType(v BaseType) bool
	setBaseTypes(v []BaseType)
	setDeclaredMembers(v *binder.SymbolTable)
}

// Class and interface types (types.TFClass and types.TFInterface)
type interfaceType struct {
	typeParams      		[]*typeParameter // Type parameters (nil if non-generic)
	resolvedBaseTypes       []BaseType       // Base types
	declaredMembers *binder.SymbolTable
	objectType
}
func (i *interfaceType) TypeParameters() []*typeParameter     { return i.typeParams }
func (i *interfaceType) DeclaredMembers() *binder.SymbolTable { return i.declaredMembers }

func (i *interfaceType) baseTypes() []BaseType                { return i.resolvedBaseTypes }
func (i *interfaceType) setTypeParameters(v []*typeParameter) { i.typeParams = v }
func (i *interfaceType) addBaseType(v BaseType)               { i.resolvedBaseTypes = append(i.resolvedBaseTypes, v) }
func (i *interfaceType) hasBaseType(v BaseType) bool {
	for _, baseType := range i.resolvedBaseTypes {
		if baseType.Type == v.Type &&
			baseType.Kind == v.Kind {
			return true
		}
	}
	return false
}
func (i *interfaceType) setBaseTypes(v []BaseType) {
	i.resolvedBaseTypes = v
}
func (i *interfaceType) setDeclaredMembers(v *binder.SymbolTable) { i.declaredMembers = v }

type TypeReference interface {
	ObjectType
	Target() *genericType
	TypeArguments() []Type

	baseTypes() []BaseType
	addBaseType(v BaseType)
	setBaseTypes(v []BaseType)
}

// Type references (types.TFReference)
type typeReference struct {
	target            *genericType // Type reference target
	typeArguments     []Type       // Type reference type arguments
	resolvedBaseTypes []BaseType   // Base types
	objectType
}
func (t *typeReference) Target() *genericType   { return t.target }
func (t *typeReference) TypeArguments() []Type 	{ return t.typeArguments }
func (t *typeReference) baseTypes() []BaseType 	{ return t.resolvedBaseTypes }
func (t *typeReference) addBaseType(v BaseType)	{ t.resolvedBaseTypes = append(t.resolvedBaseTypes, v) }
func (t *typeReference) setBaseTypes(v []BaseType) {
	t.resolvedBaseTypes = v
}

// Generic class and interface types
type genericType struct {
	target            *genericType          // Type reference target
	typeArgs          []Type                // Type reference type arguments
	instantiations map[string]TypeReference // Generic instantiation cache
	interfaceType
}
func (g *genericType) Target() *genericType  { return g.target }
func (g *genericType) TypeArguments() []Type { return g.typeArgs }


// Type parameters (types.TFTypeParameter)
type typeParameter struct {
	constraint Type           // Constraint
	target     *typeParameter // Instantiation target
	mapper     *TypeMapper    // Instantiation mapper
	tpe
}

type SignatureKind = int
const(
	SIKCall SignatureKind = iota
	SIKConstruct
)

type Signature struct {
	Declaration           ast.Declaration  // Originating declaration
	TypeParameters        []*typeParameter // Type parameters (undefined if non-generic)
	Parameters            []*binder.Symbol // Content
	Throws                []Type
	ResolvedReturnType    Type        // Resolved return type
	MinArgumentCount      int         // Number of non-optional parameters
	HasRestParameter      bool        // True if last parameter is rest parameter
	Target                *Signature  // Instantiation target
	Mapper                *TypeMapper // Instantiation mapper
	ErasedSignatureCache  *Signature  // Erased version of signature (deferred)
	IsolatedSignatureType ObjectType  // A manufactured type that just contains the signature for purposes of signature comparison
}

type TypeMapper struct {
	Invoke func(t Type) Type
}

// Incomplete types occur during control flow analysis of loops. An incompleteType
// is distinguished from a regular type by a flags value of zero. Incomplete type
// objects are internal to the getFlowTypeOfRefecence function and never escape it.
type incompleteType struct {
	TempType Type // The type marked incomplete
	tpe
}

type TypeFlags = int64

const (
	TFAny TypeFlags = 1 << iota
	TFString

	TFByte
	TFShort
	TFInt
	TFLong
	TFFloat
	TFDouble
	TFChar
	TFBoolean

	TFVoid
	TFNull
	TFNoBounds
	TFInBounds
	TFOutBounds
	TFNullable
	TFTypeParameter 	// Type parameter
	TFEnum          	// Enum type
	TFClass         	// Class
	TFAnonymousClass    // Anonymous Class
	TFInterface     	// Interface
	TFReference     	// Generic type reference
	TFAnnotation		// Static class or interface or method or function
	TFPackage       	// Package
	TFAnonymous 		// Anonymous
	TFNumber

	TFNullLike            = TFNull | TFNullable
	TFBoundsLike          = TFInBounds | TFOutBounds | TFNoBounds
	TFIntrinsic           = TFByte | TFShort | TFInt | TFLong | TFFloat | TFDouble | TFChar | TFBoolean | TFVoid | TFNull
	TFNumberLike          = TFByte | TFShort | TFInt | TFLong | TFFloat | TFDouble | TFChar
	TFObjectLike          = TFClass | TFEnum | TFAnonymousClass | TFInterface | TFAnnotation | TFReference | TFAnonymous | TFTypeParameter // | TFNoBounds | TFInBounds | TFOutBounds
	TFClassLike           = TFClass | TFEnum | TFAnonymousClass
	TFInterfaceLike       = TFInterface | TFAnnotation

	TFNarrowable = TFAny | TFTypeParameter | TFString | TFNumberLike | TFBoolean
)

func toTypes(arr interface{}) []Type {
	var result []Type
	switch n := arr.(type) {
	case []*typeParameter:
		for _, i := range n {
			result = append(result, i)
		}
	default:
		panic(fmt.Sprintf("to types not support %T", arr))
	}
	return result
}

func containsType(types []Type, _type Type) bool {
	for _, t := range types {
		if t == _type {
			return true
		}
	}
	return false
}

